package com.floreantpos.services;

import java.util.Date;
import java.util.Iterator;
import java.util.List;

import org.hibernate.Criteria;
import org.hibernate.Session;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

import com.floreantpos.main.Application;
import com.floreantpos.model.CashDropTransaction;
import com.floreantpos.model.DrawerPullReport;
import com.floreantpos.model.DrawerPullVoidTicketEntry;
import com.floreantpos.model.GiftCertificateTransaction;
import com.floreantpos.model.Gratuity;
import com.floreantpos.model.PayOutTransaction;
import com.floreantpos.model.PosTransaction;
import com.floreantpos.model.Terminal;
import com.floreantpos.model.Ticket;
import com.floreantpos.model.TicketCouponAndDiscount;
import com.floreantpos.model.dao.CashDropTransactionDAO;
import com.floreantpos.model.dao.CashTransactionDAO;
import com.floreantpos.model.dao.CreditCardTransactionDAO;
import com.floreantpos.model.dao.DebitCardTransactionDAO;
import com.floreantpos.model.dao.GenericDAO;
import com.floreantpos.model.dao.PayOutTransactionDAO;
import com.floreantpos.model.dao.RefundTransactionDAO;
import com.floreantpos.model.dao.TicketDAO;
import com.floreantpos.model.util.RefundSummary;
import com.floreantpos.model.util.TransactionSummary;

public class DrawerPullService {

  public DrawerPullReport getDrawerPullReport() throws Exception {
    Session session = null;
    try {
      Terminal terminal = Application.getInstance().getTerminal();
      DrawerPullReport report = new DrawerPullReport();
      report.setReportTime(new Date());

      GenericDAO dao = new GenericDAO();
      session = dao.createNewSession();

      Criteria criteria = session.createCriteria(Ticket.class, "t");
      criteria.add(Restrictions.eq(Ticket.PROP_PAYMENT_TICKET, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_CLOSED, Boolean.TRUE));
      criteria.add(Restrictions.eq(Ticket.PROP_VOIDED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_DRAWER_RESETTED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_TERMINAL, terminal));

      ProjectionList projectionList = Projections.projectionList();
      projectionList.add(Projections.rowCount());
      projectionList.add(Projections.sum(Ticket.PROP_SUBTOTAL_AMOUNT));
      projectionList.add(Projections.sum(Ticket.PROP_DISCOUNT_AMOUNT));
      projectionList.add(Projections.sum(Ticket.PROP_TAX_AMOUNT));

      criteria.setProjection(projectionList);
      List list = criteria.list();

      double subtotal = 0;
      double discount = 0;
      double salesTax = 0;
      if (list != null && list.size() > 0) {
        Object[] objects = (Object[]) list.get(0);

        if (objects.length > 0 && objects[0] != null) {
          int count = ((Number) objects[0]).intValue();
          report.setTicketCount(count);
        }
        if (objects.length > 1 && objects[1] != null) {
          subtotal = ((Number) objects[1]).doubleValue();
        }
        if (objects.length > 2 && objects[2] != null) {
          discount = ((Number) objects[2]).doubleValue();
        }
        if (objects.length > 3 && objects[3] != null) {
          salesTax = ((Number) objects[3]).doubleValue();
        }
      }
      report.setNetSales(subtotal - discount);
      report.setSalesTax(salesTax);

      criteria = session.createCriteria(Ticket.class);
      criteria.add(Restrictions.eq(Ticket.PROP_CLOSED, Boolean.TRUE));
      criteria.add(Restrictions.eq(Ticket.PROP_VOIDED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_DRAWER_RESETTED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_TERMINAL, terminal));
      criteria.add(Restrictions.eq(Ticket.PROP_TRANSACTION_TYPE, PosTransaction.TYPE_GIFT_CERT));
      projectionList = Projections.projectionList();
      projectionList.add(Projections.rowCount());
      projectionList.add(Projections.sum(Ticket.PROP_SUBTOTAL_AMOUNT));
      criteria.setProjection(projectionList);
      list = criteria.list();
      if (list.size() > 0) {
        Object[] datas = (Object[]) list.get(0);
        if (datas.length > 0 && datas[0] instanceof Number) {
          report.setGiftCertReturnCount(((Number) datas[0]).intValue());
        }
        if (datas.length > 1 && datas[1] instanceof Number) {
          report.setGiftCertReturnAmount(((Number) datas[1]).doubleValue());
        }
      }

      double tips = 0;
      criteria = session.createCriteria(Gratuity.class);
      criteria.createAlias(Gratuity.PROP_TICKET, "ticket");
      criteria.add(Restrictions.eq(Gratuity.PROP_TERMINAL, terminal));
      criteria.add(Restrictions.eq("ticket." + Ticket.PROP_DRAWER_RESETTED, Boolean.FALSE));
      criteria.setProjection(Projections.sum(Gratuity.PROP_AMOUNT));
      list = criteria.list();
      if (list.size() > 0 && list.get(0) instanceof Number) {
        tips = ((Number) list.get(0)).doubleValue();
      }
      report.setChargedTips(tips);

      criteria = session.createCriteria(Ticket.class, "t");
      criteria.add(Restrictions.eq(Ticket.PROP_CLOSED, Boolean.TRUE));
      criteria.add(Restrictions.eq(Ticket.PROP_VOIDED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_DRAWER_RESETTED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_TRANSACTION_TYPE, PosTransaction.TYPE_CASH));
      criteria.add(Restrictions.eq(Ticket.PROP_TERMINAL, terminal));

      projectionList = Projections.projectionList();
      projectionList.add(Projections.rowCount());
      projectionList.add(Projections.sum(Ticket.PROP_TAX_AMOUNT));

      criteria.setProjection(projectionList);
      list = criteria.list();

      double cashTax = 0;
      if (list != null && list.size() > 0) {
        Object[] objects = (Object[]) list.get(0);

        if (objects.length > 1 && objects[1] != null) {
          cashTax = ((Number) objects[1]).doubleValue();
        }
      }
      report.setCashTax(cashTax);

      TransactionSummary cashTransactionSummary = CashTransactionDAO.getInstance().getTransactionSummary(terminal);
      TransactionSummary creditCardTransactionSummary = CreditCardTransactionDAO.getInstance().getTransactionSummary(terminal);
      TransactionSummary debitCardTransactionSummary = DebitCardTransactionDAO.getInstance().getTransactionSummary(terminal);

      report.setCashReceiptNumber(cashTransactionSummary.getTotalNumber());
      report.setCashReceiptAmount(cashTransactionSummary.getTotalAmount() + cashTransactionSummary.getGratuityAmount());

      report.setCreditCardReceiptNumber(creditCardTransactionSummary.getTotalNumber());
      report.setCreditCardReceiptAmount(creditCardTransactionSummary.getTotalAmount() + creditCardTransactionSummary.getGratuityAmount());

      report.setDebitCardReceiptNumber(debitCardTransactionSummary.getTotalNumber());
      report.setDebitCardReceiptAmount(debitCardTransactionSummary.getTotalAmount() + debitCardTransactionSummary.getGratuityAmount());

      RefundSummary refundSummary = new RefundTransactionDAO().getTotalRefundForTerminal(terminal);
      report.setCashBack(refundSummary.getAmount());

      report.setTipsPaid(TicketDAO.getInstance().getPaidGratuityAmount(terminal));

      double totalPayout = 0;
      List<PayOutTransaction> payoutTransactions = new PayOutTransactionDAO().getUnsettled(terminal);
      for (PayOutTransaction transaction : payoutTransactions) {
        totalPayout += transaction.getTotalAmount();
      }
      report.setPayOutNumber(payoutTransactions.size());
      report.setPayOutAmount(totalPayout);

      double drawerBleedAmount = 0;
      List<CashDropTransaction> cashDrops = new CashDropTransactionDAO().findUnsettled(terminal);
      for (CashDropTransaction transaction : cashDrops) {
        drawerBleedAmount += transaction.getTotalAmount();
      }
      report.setDrawerBleedNumber(cashDrops.size());
      report.setDrawerBleedAmount(drawerBleedAmount);

      report.setBeginCash(terminal.getOpeningBalance());
      report.setCashToDeposit(terminal.getCurrentBalance());

      criteria = session.createCriteria(Ticket.class, "t");
      criteria.add(Restrictions.eq(Ticket.PROP_VOIDED, Boolean.TRUE));
      criteria.add(Restrictions.eq(Ticket.PROP_DRAWER_RESETTED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_TERMINAL, terminal));
      list = criteria.list();

      for (Iterator iter = list.iterator(); iter.hasNext();) {
        Ticket ticket = (Ticket) iter.next();
        DrawerPullVoidTicketEntry entry = new DrawerPullVoidTicketEntry();
        entry.setCode(ticket.getId());
        entry.setAmount(ticket.getSubtotalAmount());
        entry.setReason(ticket.getVoidReason());

        report.addVoidTicketEntry(entry);
      }

      criteria = session.createCriteria(GiftCertificateTransaction.class);
      criteria.add(Restrictions.eq(GiftCertificateTransaction.PROP_DRAWER_RESETTED, Boolean.FALSE));
      criteria.add(Restrictions.eq(GiftCertificateTransaction.PROP_TERMINAL, terminal));
      projectionList = Projections.projectionList();
      projectionList.add(Projections.sum(GiftCertificateTransaction.PROP_FACE_VALUE));
      projectionList.add(Projections.sum(GiftCertificateTransaction.PROP_CASH_BACK_AMOUNT));
      criteria.setProjection(projectionList);
      Object[] o = (Object[]) criteria.uniqueResult();
      if (o.length > 0 && o[0] instanceof Number) {
        double amount = ((Number) o[0]).doubleValue();
        report.setGiftCertReturnAmount(amount);
      }
      if (o.length > 1 && o[1] instanceof Number) {
        double amount = ((Number) o[1]).doubleValue();
        report.setGiftCertChangeAmount(amount);
      }

      int totalDiscountCount = 0;
      double totalDiscountAmount = 0;
      double totalDiscountSales = 0;
      int totalDiscountGuest = 0;
      int totalDiscountPartySize = 0;
      int totalDiscountCheckSize = 0;
      double totalDiscountPercentage = 0;
      double totalDiscountRatio = 0;

      criteria = session.createCriteria(Ticket.class, "t");
      criteria.add(Restrictions.eq(Ticket.PROP_CLOSED, Boolean.TRUE));
      criteria.add(Restrictions.eq(Ticket.PROP_VOIDED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_DRAWER_RESETTED, Boolean.FALSE));
      criteria.add(Restrictions.eq(Ticket.PROP_TERMINAL, terminal));
      list = criteria.list();

      for (Iterator iter = list.iterator(); iter.hasNext();) {
        Ticket ticket = (Ticket) iter.next();
        if (ticket.getCouponAndDiscounts() != null) {
          List<TicketCouponAndDiscount> discounts = ticket.getCouponAndDiscounts();
          for (TicketCouponAndDiscount discount2 : discounts) {
            ++totalDiscountCount;
            totalDiscountAmount += discount2.getValue();
            totalDiscountGuest += ticket.getNumberOfGuests();
            totalDiscountSales += ticket.getTotalAmount();
            totalDiscountCheckSize++;
          }
        }
      }

      totalDiscountPartySize = totalDiscountGuest;

      report.setTotalDiscountCount(totalDiscountCount);
      report.setTotalDiscountAmount(totalDiscountAmount);
      report.setTotalDiscountCheckSize(totalDiscountCheckSize);
      report.setTotalDiscountSales(totalDiscountSales);
      report.setTotalDiscountGuest(totalDiscountGuest);
      report.setTotalDiscountPartySize(totalDiscountPartySize);
      report.setTotalDiscountPercentage(totalDiscountPercentage);
      report.setTotalDiscountRatio(totalDiscountRatio);

      report.setTerminal(terminal);

      report.calculate();

      return report;
    } finally {
      if (session != null) {
        session.close();
      }
    }
  }
}
